#include "Interpretator.h"

#include "ErrorCodes.h"

#include <QFileInfo>
#include <QDir>

#define END QStringLiteral("end")
#define OBJECT QStringLiteral("object")
#define ROOM QStringLiteral("room")
#define NAMES QStringLiteral("names")
#define COMMON QStringLiteral("common")
#define CUSTOM QStringLiteral("custom")
#define EVENT QStringLiteral("event")
#define ACTION QStringLiteral("action")
#define IMPLEMENTATION QStringLiteral("implementation")
#define DECLARATION QStringLiteral("declaration")
#define INIT QStringLiteral("init")
#define DEINIT QStringLiteral("deinit")

Interpretator::Interpretator(QObject *parent):QObject(parent),_state(NoClass),
    _row(0),_inited(false),_deinited(false),out(stdout){

}

int Interpretator::translateFKOL(const char *path,const bool& option){
    QFile f(path);
    if(!f.open(QIODevice::ReadOnly))return ErrorCode::canNotOpenFile;
    QTextStream stream(&f);
    while(!stream.atEnd()){
        ++_row;
        int r=processLine(stream.readLine());
        if(r!=ErrorCode::noError)return r;
    }
    if(_state!=NoClass)return ErrorCode::parsingError;
    QFileInfo info(f);
    return saveCppCode(info.dir().absolutePath(),option);
}

int Interpretator::processLine(QString line){
    line=line.trimmed();
    if(line.isEmpty())return ErrorCode::noError;
//if comment was found
    if(line.left(2)==QString("//")){
        line.remove(0,2);
        return processComment(line);
//if raw code was found
    }else if(line.left(1)==QString("/")){
        line.remove(0,1);
        return processRawCode(line);
    }else{
//if command was found
        return processCommand(line);
    }
}

int Interpretator::processComment(QString line){
    Q_UNUSED(line)
    return ErrorCode::noError;
}

int Interpretator::processRawCode(QString line){
    switch(_state){
//    case WaitImplementation:
//        _state=InImplementation;
    case InImplementation:
        _currentImplementation.append(line+QString("\n"));
        return ErrorCode::noError;
    default:
        out<<QString(tr("Parsing error at line %1: unexpected raw code\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
}

int Interpretator::processCommand(QString line){
    QStringList lst(line.split(":"));
    QString command, arguments;
    if(!lst.isEmpty())command=lst.takeFirst().trimmed();
    if(!lst.isEmpty())arguments=lst.join(":").trimmed();
    if(command==NAMES){
        return processNameLibrary(arguments);
    }else if(command==OBJECT){
        return processObject(arguments);
    }else if(command==DECLARATION){
        return processDeclaration(arguments);
    }else if(command==CUSTOM){
        return processCustom(arguments);
    }else if(command==END){
        return processEnd(arguments);
    }else{
        out<<QString(tr("Parsing error at line %1: unrecognized command\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
}

int Interpretator::processNameLibrary(QString arguments){
    QRegExp check(QString("[A-Z]+[a-zA-Z0-9]*"));
    if(check.exactMatch(arguments)){
        _nameLibrary=arguments;
        if(_state==InImplementation)pushImplementation();
        if(_state!=NoClass)_state=NoImplementation;
        _nameLibraryIncluder.append(QString("#include \"%1.h\"\n").arg(arguments));
        return ErrorCode::noError;
    }
    out<<QString(tr("Parsing error at line %1: invalid name library argument\n")).arg(QString::number(_row));
    return ErrorCode::parsingError;
}

int Interpretator::processObject(QString arguments){
    if(_state!=NoClass){
        out<<QString(tr("Parsing error at line %1: nested object declarations are not allowed\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QStringList lst=arguments.split(",");
    QString arg1,arg2("FKObject");
    if(!lst.isEmpty())arg1=lst.takeFirst().trimmed();
    if(!lst.isEmpty())arg2=lst.takeFirst().trimmed();
    if(!lst.isEmpty()){
        out<<QString(tr("Parsing error at line %1: too many arguments provided\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QRegExp check(QString("[A-Z]+[a-zA-Z0-9]*"));
    if(!check.exactMatch(arg1)){
        out<<QString(tr("Parsing error at line %1: invalid class name\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(!check.exactMatch(arg2)){
        out<<QString(tr("Parsing error at line %1: invalid parent class name\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    _state=NoImplementation;

    _headerBegin.append(QString(
"#ifndef %1_H\n"
"#define %1_H\n"
"\n"
"#include \"%2.h\"\n"
                       ).arg(arg1.toUpper()).arg(arg2));

    _classDeclarator.append(QString(
"\n"
"class FK_OBJECT(%1,%2)\n"
    "\tQ_OBJECT\n"
                       ).arg(arg1).arg(arg2));

    _classEnd1.append(QString(
"protected:\n"
    "\tvoid customInitialization();\n"
    "\tvoid customDeinitialization();\n"
"private:\n"
    "\tstruct Servant{\n"));

    _classEnd2.append(QString(
    "\t};\n"
    "\tstruct CustomValues{\n"));

    _classEnd3.append(QString(
    "\t};\n"
"};\n"
"\n"
"#endif"));

    _beginImplementation.append(QString(
"#include \"%1.h\"\n"
"\n"
"#include \"FKLogger.h\"\n"
"\n"
"FK_EVENTS(%1){\n").arg(arg1));

    _currentImplementation.append(QString(
"}\n"
"\n"
"%1::%1(FKRoom* room,qint32 objectID):%2(room,objectID){\n"
    "\tFK_CBEGIN\n"
    "\tFK_CONSTRUCTOR\n"
    "\tFK_CEND\n"
"}\n"
"\n"
"%1::~%1(){\n"
    "\tFK_DBEGIN\n"
    "\tFK_DESTRUCTOR\n"
    "\tFK_DEND\n"
"}\n"
"\n"
"void %1::customInitialization(){\n"
"}\n"
"\n"
"void %1::customDeinitialization(){\n"
"}\n"
"\n"
                                      ).arg(arg1).arg(arg2));
    _className=arg1;
    return ErrorCode::noError;
}

int Interpretator::processDeclaration(QString arguments){
    if(_state==NoClass){
        out<<QString(tr("Parsing error at line %1: out of class declarations are not allowed\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QStringList args=arguments.split(",.");
    QStringList args1=args.takeFirst().split(",");
    QString ret("void"), func, arg;
    func=args1.takeFirst().trimmed();
    if(!args1.isEmpty())ret=args1.takeFirst().trimmed();
    if(!args1.isEmpty()){
        out<<QString(tr("Parsing error at line %1: too many primary arguments\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(!args.isEmpty())arg=args.takeFirst().trimmed();
    if(!args.isEmpty()){
        out<<QString(tr("Parsing error at line %1: too many arguments\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(_state==InImplementation)pushImplementation();
    _state=InImplementation;
    QRegExp check(QString("[a-zA-Z]+[a-zA-Z0-9\\*]*"));
    if(!check.exactMatch(ret)){
        out<<QString(tr("Parsing error at line %1: invalid return type\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QRegExp check1(QString("[a-z]+[a-zA-Z0-9]*"));
    if(!check1.exactMatch(func)){
        out<<QString(tr("Parsing error at line %1: invalid function name\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    _declarators.append(QString("\t%2 %1(%3);\n").arg(func).arg(ret).arg(arg));
    _currentImplementation.append(QString("%2 %4::%1(%3){\n").arg(func).arg(ret).arg(arg).arg(_className));
    return ErrorCode::noError;
}

int Interpretator::processCustom(QString arguments){
    if(_state==NoClass){
        out<<QString(tr("Parsing error at line %1: out of class declarations are not allowed\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QStringList args=arguments.split(",");
    QString var, prop;
    prop=args.takeFirst().trimmed();
    if(args.isEmpty()){
        out<<QString(tr("Parsing error at line %1: not enough arguments\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    var=args.takeFirst().trimmed();
    if(!args.isEmpty()){
        out<<QString(tr("Parsing error at line %1: too many arguments\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QRegExp check(QString("[a-zA-Z]+[a-zA-Z0-9]*"));
    if(!check.exactMatch(var)){
        out<<QString(tr("Parsing error at line %1: invalid property type\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    QRegExp check1(QString("[a-z]+[a-zA-Z0-9]*"));
    if(!check1.exactMatch(prop)){
        out<<QString(tr("Parsing error at line %1: invalid property name\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(_nameLibrary==QString()){
        out<<QString(tr("Parsing error at line %1: define name library first\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(_state==InImplementation)pushImplementation();
    _state=NoImplementation;
    QString capsedProp=prop;
    capsedProp[0]=capsedProp[0].toUpper();
    _customVals.append(QString("\t\t%2 %1;\n").arg(prop).arg(var));
    _customRealVals.append(QString("\t\t%2 %1;\n").arg(prop).arg(var));
    _customSetters.append(QString("\tvoid set%1(const %2& v){if(servant)servant->%3=v;updateProp(%4::c_%3);}\n").arg(capsedProp).arg(var).arg(prop).arg(_nameLibrary));
//    _customSettersImplementation;
    _customGetters.append(QString("\t%2 get%1()const{return servant ? servant->%3 : %2();}\n").arg(capsedProp).arg(var).arg(prop));
//    _customGettersImplementation;
    _customTakers.append(QString("\t%2 %1()const{return customValues ? customValues->%1 : %2();}\n").arg(prop).arg(var));
//    _customTakersImplementation;
    _customChangers.append(QString("\tvoid c_%1(const %2& v){if(customValues)customValues->%1=v;}\n").arg(prop).arg(var));
//    _customChangersImplementation
    _customRegistrators.append(QString("props.append(%2::c_%1);\n").arg(prop).arg(_nameLibrary));
    _customDeclarators.append(QString("\tQ_PROPERTY(%2 C_%4 READ get%3 WRITE c_%1)\n").arg(prop).arg(var).arg(capsedProp).arg(prop.toUpper()));
    return ErrorCode::noError;
}

void Interpretator::pushImplementation(){
    _currentImplementation.append(QString("}\n\n"));
}

int Interpretator::processEnd(QString arguments){
    if(arguments!=QString()){
        out<<QString(tr("Parsing error at line %1: arguments provided for end command\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(_state==NoClass){
        out<<QString(tr("Parsing error at line %1: unexpected end command\n")).arg(QString::number(_row));
        return ErrorCode::parsingError;
    }
    if(_state==InImplementation)pushImplementation();
    _state=NoClass;
    return ErrorCode::noError;
}

int Interpretator::saveCppCode(const QString &path, const bool &option){
    QFile h(path+QString("/%1.h").arg(_className));
    QFile cpp(path+QString("/%1.cpp").arg(_className));
    bool exist=h.exists() || cpp.exists();
    if(exist && !option){
        out<<QString(tr("Files already exist. Use -o option to override them\n")).arg(QString::number(_row));
        return ErrorCode::canNotSaveFile;
    }
    if(!h.open(QIODevice::WriteOnly)){
        out<<QString(tr("Can not open header file for write data\n")).arg(QString::number(_row));
        return ErrorCode::canNotSaveFile;
    }
    if(!cpp.open(QIODevice::WriteOnly)){
        out<<QString(tr("Can not open cpp file for write data\n")).arg(QString::number(_row));
        return ErrorCode::canNotSaveFile;
    }
    QTextStream hstream(&h), cppstream(&cpp);
    hstream<<_headerBegin <<
             _nameLibraryIncluder <<
             _classDeclarator <<
             _customDeclarators <<
             QString("public:\n") <<
             _customSetters <<
             _customGetters <<
             _customTakers <<
             _declarators <<
             QString("private:\n") <<
             _customChangers <<
             _classEnd1 <<
             _customRealVals <<
             _classEnd2 <<
             _customVals <<
             _classEnd3;
    cppstream<<_beginImplementation <<
               _customRegistrators <<
               _currentImplementation;
    return ErrorCode::noError;
}
